#include "MaterialOperationNodeModel.h"

#include "Output/Console.h"
#include "MaterialGraph/Pin/MaterialGraphPinModel.h"

void RMaterialOperationNodeModel::FirstTimeInitializeObject()
{
    auto OutputPin = NewObject<RMaterialGraphPinModel>(this);
    OutputPin->ValueType = EMaterialGraphValueType::Vector1D;
    OutputPin->bIsOutput = true;
    Outputs.Add(OutputPin);

    //create 3 input pins
    auto InputPinA = NewObject<RMaterialGraphPinModel>(this);
    InputPinA->ValueType = EMaterialGraphValueType::Vector1D;
    InputPinA->bIsOutput = false;
    Inputs.Add(InputPinA);

    auto InputPinB = NewObject<RMaterialGraphPinModel>(this);
    InputPinB->ValueType = EMaterialGraphValueType::Vector1D;
    InputPinB->bIsOutput = false;
    Inputs.Add(InputPinB);

    auto InputPinC = NewObject<RMaterialGraphPinModel>(this);
    InputPinC->ValueType = EMaterialGraphValueType::Vector1D;
    InputPinC->bIsOutput = false;
    Inputs.Add(InputPinC);

    UpdatePin();
}

CString RMaterialOperationNodeModel::GenerateExpression()
{
    switch(OperationType)
    {
        case EMaterialOperationType::Add:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" + "+InputB+U";";
        }

        case EMaterialOperationType::Subtract:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" - "+InputB+U";";
        }

        case EMaterialOperationType::Multiply:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" * "+InputB+U";";
        }

        case EMaterialOperationType::Divide:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" / "+InputB+U";";
        }

        case EMaterialOperationType::AddAfterMultiply:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            CString InputC = Inputs[2]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" * "+InputB+U" + "+InputC+U";";
        }

        case EMaterialOperationType::Logarithm:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = log("+InputA+U");";
        }

        case EMaterialOperationType::SquareRoot:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = sqrt("+InputA+U");";
        }

        case EMaterialOperationType::Inverse:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = 1.0 / "+InputA+U";";
        }

        case EMaterialOperationType::InverseSquareRoot:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = 1.0 / sqrt("+InputA+U");";
        }

        case EMaterialOperationType::Absolute:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = abs("+InputA+U");";
        }

        case EMaterialOperationType::Power:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = pow("+InputA+U", "+InputB+U");";
        }

        case EMaterialOperationType::Min:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = min("+InputA+U", "+InputB+U");";
        }

        case EMaterialOperationType::Max:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = max("+InputA+U", "+InputB+U");";
        }

        case EMaterialOperationType::Greater:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" > "+InputB+U"? 1.0 : 0.0;";
        }

        case EMaterialOperationType::Less:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+InputA+U" < "+InputB+U"? 1.0 : 0.0;";
        }

        case EMaterialOperationType::Sign:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = sign("+InputA+U");";
        }

        case EMaterialOperationType::Equal:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            CString InputC = Inputs[2]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = abs("+InputA+U" - "+InputB+U") < "+InputC+U"? 1.0 : 0.0;";
        }

        case EMaterialOperationType::SmoothMin:
        {
            CString A = Inputs[0]->GetValueExpression();
            CString B = Inputs[1]->GetValueExpression();
            CString K = Inputs[2]->GetValueExpression();

            CString Result=U"float "+Outputs[0]->UniqueOutputPinName+U";\n";
            Result+=U"{\n";
            Result+=U"    "+K+U" *= 2.0;\n";
            Result+=U"    float x = "+B+U" - "+A+U";\n";
            Result+=U"    "+Outputs[0]->UniqueOutputPinName+U" = 0.5 * ("+A+U" + "+B+U" - sqrt(x * x + "+K+U" * "+K+U"));\n";
            Result+=U"}\n";
            return Result;
        }

        case EMaterialOperationType::SmoothMax:
        {
            CString A = Inputs[0]->GetValueExpression();
            CString B = Inputs[1]->GetValueExpression();
            CString K = Inputs[2]->GetValueExpression();

            CString Result=U"float "+Outputs[0]->UniqueOutputPinName+U";\n";
            Result+=U"{\n";
            Result+=U"    "+K+U" *= 2.0;\n";
            Result+=U"    float x = "+B+U" - "+A+U";\n";
            Result+=U"    "+Outputs[0]->UniqueOutputPinName+U" = 0.5 * ("+A+U" + "+B+U" + sqrt(x * x + "+K+U" * "+K+U"));\n";
            Result+=U"}\n";
            return Result;
        }

        case EMaterialOperationType::Floor:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = floor("+InputA+U");";
        }

        case EMaterialOperationType::Ceil:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = ceil("+InputA+U");";
        }

        case EMaterialOperationType::Round:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = round("+InputA+U");";
        }

        case EMaterialOperationType::Integer:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = float(int("+InputA+U"));";
        }

        case EMaterialOperationType::Fraction:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = fract("+InputA+U");";
        }

        case EMaterialOperationType::Snap:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            CString InputB = Inputs[1]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = round("+InputA+U" / "+InputB+U") * "+InputB+U";";
        }

        case EMaterialOperationType::Loop:
        {
            CString X = Inputs[0]->GetValueExpression();
            CString Min=Inputs[1]->GetValueExpression();
            CString Max=Inputs[2]->GetValueExpression();

            return U"float "+Outputs[0]->UniqueOutputPinName+U" = "+X+U" - floor(("+X+U" - "+Min+U") / ("+Max+U" - "+Min+U")) * ("+Max+U" - "+Min+U");";
        }

        case EMaterialOperationType::Sin:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = sin("+InputA+U");";
        }

        case EMaterialOperationType::Cos:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = cos("+InputA+U");";
        }

        case EMaterialOperationType::Tan:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = tan("+InputA+U");";
        }

        case EMaterialOperationType::Asin:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = asin("+InputA+U");";
        }

        case EMaterialOperationType::Acos:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = acos("+InputA+U");";
        }

        case EMaterialOperationType::Atan:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = atan("+InputA+U");";
        }

        case EMaterialOperationType::Atan2:
        {
            CString Y=Inputs[0]->GetValueExpression();
            CString X=Inputs[1]->GetValueExpression();

            return U"float "+Outputs[0]->UniqueOutputPinName+U" = atan("+Y+U", "+X+U");";
        }

        case EMaterialOperationType::Sinh:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = sinh("+InputA+U");";
        }

        case EMaterialOperationType::Cosh:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = cosh("+InputA+U");";
        }

        case EMaterialOperationType::Tanh:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = tanh("+InputA+U");";
        }

        case EMaterialOperationType::ToRadians:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = radians("+InputA+U");";
        }

        case EMaterialOperationType::ToDegrees:
        {
            CString InputA = Inputs[0]->GetValueExpression();
            return U"float "+Outputs[0]->UniqueOutputPinName+U" = degrees("+InputA+U");";
        }



        default:
        {
            //unreachable
            assert(false);
            return U"";
        }
    }
}

void RMaterialOperationNodeModel::UpdatePin()
{

    switch(OperationType)
    {
        case EMaterialOperationType::SquareRoot:
        case EMaterialOperationType::Inverse:
        case EMaterialOperationType::InverseSquareRoot:
        case EMaterialOperationType::Absolute:

        case EMaterialOperationType::Sign:

        case EMaterialOperationType::Floor:
        case EMaterialOperationType::Ceil:
        case EMaterialOperationType::Round:
        case EMaterialOperationType::Integer:
        case EMaterialOperationType::Fraction:

        case EMaterialOperationType::Sin:
        case EMaterialOperationType::Cos:
        case EMaterialOperationType::Tan:
        case EMaterialOperationType::Asin:
        case EMaterialOperationType::Acos:
        case EMaterialOperationType::Atan:
        case EMaterialOperationType::Sinh:
        case EMaterialOperationType::Cosh:
        case EMaterialOperationType::Tanh:

        case EMaterialOperationType::ToRadians:
        case EMaterialOperationType::ToDegrees:
        {
            ValidInputNum=1;
            break;
        }

        case EMaterialOperationType::Add:
        case EMaterialOperationType::Subtract:
        case EMaterialOperationType::Multiply:
        case EMaterialOperationType::Divide:

        case EMaterialOperationType::Logarithm:
        case EMaterialOperationType::Power:
        
        case EMaterialOperationType::Min:
        case EMaterialOperationType::Max:
        case EMaterialOperationType::Greater:
        case EMaterialOperationType::Less:



        case EMaterialOperationType::Snap:

        case EMaterialOperationType::Atan2:
        {
            ValidInputNum=2;
            break;
        }

        case EMaterialOperationType::AddAfterMultiply:

        case EMaterialOperationType::Equal:
        case EMaterialOperationType::SmoothMin:
        case EMaterialOperationType::SmoothMax:

        case EMaterialOperationType::Loop:
        {
            ValidInputNum=3;
            break;
        }

        default:
        {
            //unreachable
            assert(false);
        }
    }

    switch(ValidInputNum)
    {
        case 1:
        {
            Inputs[0]->bIsHidden = false;
            Inputs[1]->bIsHidden = true;
            Inputs[2]->bIsHidden = true;
            break;
        }

        case 2:
        {
            Inputs[0]->bIsHidden = false;
            Inputs[1]->bIsHidden = false;
            Inputs[2]->bIsHidden = true;
            break;
        }

        case 3:
        {
            Inputs[0]->bIsHidden = false;
            Inputs[1]->bIsHidden = false;
            Inputs[2]->bIsHidden = false;
            break;
        }
    
        default:
        {
            //unreachable
            assert(false);
        }
    }
}
